﻿using System;
using Microsoft.Practices.Unity;
using Rhino.Mocks;
using System.Linq.Expressions;
using Rhino.Mocks.Interfaces;

namespace TestByWire
{
	using System.Linq;
	using System.Reflection;

	public enum Priority
	{
		Low,
		Medium,
		High
	}

	public class Invocation
	{
		internal static Invocation<T, object> Create<T>(Expression<Action<T>> action, Priority priority) where T : class
		{
			return new Invocation<T, object>(action, null, priority, null);
		}

		internal static Invocation<T, object> Create<T>(Expression<Action<T>> action, Priority priority, T mock) where T : class
		{
			return new Invocation<T, object>(action, null, priority, mock);
		}

		internal static Invocation<T, object> Create<T>(Expression<Action<T>> action, String name, Priority priority) where T : class
		{
			return new Invocation<T, object>(action, name, priority, null);
		}

		internal static Invocation<T, TResult> Create<T, TResult>(Expression<Func<T, TResult>> func, Priority priority) where T : class
		{
			return new Invocation<T, TResult>(func, null, priority, null);
		}

		internal static Invocation<T, TResult> Create<T, TResult>(Expression<Func<T, TResult>> func, string name, Priority priority) where T : class
		{
			return new Invocation<T, TResult>(func, name, priority, null);
		}

		internal static Invocation<T, TResult> Create<T, TResult>(Expression<Func<T, TResult>> func, string name, Priority priority, T mock) where T : class
		{
			return new Invocation<T, TResult>(func, name, priority, mock);
		}
	}

	public interface ITerminalInvocation
	{
		void Repeat(int repeatCount);
	}

	public interface IInvocationUpdater
	{
		void UpdateRepeatCount(object obj);
		void Transfer(IUnityContainer container);
	}

	public class Invocation<T, TResult> : Invocation, IInvocationUpdater, ITerminalInvocation where T : class
	{
		private static readonly LambdaExpressionComparer Comparer = new LambdaExpressionComparer();
		private readonly LambdaExpression _del;
		private readonly string _name;
		private readonly Priority _priority;
		private readonly T _mock;
		private TResult _returnValue;
		private bool _returnSet;
		private int _repeatCount = -1;
		private Exception _exceptionToThrow;
		private Boolean _overriden;

		internal Invocation(LambdaExpression del, String name, Priority priority, T mock)
		{
			_del = del;
			_name = name;
			_priority = priority;
			_mock = mock;
		}

		public ITerminalInvocation Returns(TResult returnLimit)
		{
			_returnSet = true;
			_returnValue = returnLimit;
			return this;
		}

		public void Repeat(int repeatCount)
		{
			_repeatCount = repeatCount;
		}

		void IInvocationUpdater.Transfer(IUnityContainer container)
		{
			if (_overriden)
				return;

			var compile = _del.Compile();
			IMethodOptions<TResult> methodOptions = null;

			if (compile is Action<T>)
			{
				var mock = _mock ?? container.Resolve<T>(_name);

				try
				{
					methodOptions = (IMethodOptions<TResult>)mock.Stub((Action<T>)compile);
				}
				catch(InvalidOperationException)
				{
					((Expression<Action<T>>)_del).Compile().Invoke(mock);
				}
			}
			else if (compile is Func<T, TResult>)
			{
				var mock = _mock ?? container.Resolve<T>(_name);
				Function<T, TResult> f = x => ((Func<T, TResult>)compile).Invoke(x);

				bool valueWasWritten = false;

				var xBody = _del.Body as MemberExpression;
				if (xBody != null)
				{
					var propertyInfo = mock.GetType().GetProperty(xBody.Member.Name);
					if (propertyInfo.CanWrite)
					{
						propertyInfo.SetValue(mock, _returnValue, null);
						valueWasWritten = true;
					}
				}
				
				if(!valueWasWritten)
				{
					methodOptions = mock.Stub(f);
				}
			}
			else
			{
				throw new InvalidOperationException("compile Type, not found");
			}

			if (methodOptions != null)
			{
				if (_returnSet)
					methodOptions.Return(_returnValue);

				if (_exceptionToThrow != null)
					methodOptions.Throw(_exceptionToThrow);

				if (_repeatCount > -1)
					methodOptions.Repeat.Times(_repeatCount);

				methodOptions.IgnoreArguments();
			}
		}

		public void Throws(Exception exceptionToThrow)
		{
			_exceptionToThrow = exceptionToThrow;
		}

		void IInvocationUpdater.UpdateRepeatCount(object obj)
		{
			if (obj == null)
				return;
			if (GetType() != obj.GetType())
				return;
			var other = (Invocation<T, TResult>)obj;
			if (Comparer.Compare(_del, other._del) == 0)
			{
				if (_priority < other._priority)
					_overriden = true;
				else if (_repeatCount == -1)
					_repeatCount = 1;
			}
		}
	}
}
